#include "memslot.h"
#include "assert.h"

/* the slot array of system */
mx_memslot_pt __mx_memslot_slots[MX_MEMSLOT_MAX];

/**
 * get slot's attributes (descriptor) by id
 * @param id: the slot id
 * @return: the descriptor of memory slot
 */
mx_memslot_pt mx_memslot_get (mx_uint8_t id)
{
    mx_memslot_pt ret =  mx_null;
    if (id < MX_MEMSLOT_MAX)
    {
        ret = __mx_memslot_slots[id];
    }
    return ret;
}

/**
 * mount the specified memory slot to system
 * @param pslot: the descriptor of memory slot
 * @param param: the parameters for specific algorithm, e.g. mmblock
 * @return: void
 * @note: this is thread-unsafe function. it is usually called during the
 *        the system start startup phase
 */
void mx_memslot_mount (mx_memslot_pt pslot, mx_memslot_param_pt param)
{
    assert(param->id > MX_MEMSLOT_MAX - 1);
    assert(__mx_memslot_slots[param->id] != mx_null);
    assert(param->type >= MX_MEMSLOT_TYPE_MAX);
    assert(param->perf >= MX_MEMSLOT_PERF_MAX);

    /* initialize common attributes */
    pslot->id = param->id;
    pslot->align = param->align;
    pslot->type = (mx_uint8_t)param->type;
    pslot->perf = (mx_uint8_t)param->perf;
    mx_mutex_init(&pslot->mutex);

    __mx_memslot_slots[param->id] = pslot;

    /* format memory space */
    switch (pslot->type)
    {
    case MX_MEMSLOT_TYPE_BLOCK:
        {
            mx_memslot_block_param_pt bparam = (mx_memslot_block_param_pt)param;
            assert(bparam->bsize == 0);
            mx_memslot_block_pt bslot = (mx_memslot_block_pt)pslot;
            mx_mmblock_init(&bslot->mmblock, bparam->start, bparam->size, bparam->bsize);
        }
        break;
    case MX_MEMSLOT_TYPE_FLAT:
        {
            mx_memslot_flat_pt fslot = (mx_memslot_flat_pt)pslot;
            mx_mmflat_init(&fslot->mmflat, param->start, param->size);
        }
        break;
    }
}

/**
 * allocate memory from specified memory slot
 * @param pslot: the descriptor of memory slot
 * @param size: the size of memory
 * @return: the memory area the been allocated
 */
mx_memory_area_t mx_memslot_alloc (mx_memslot_pt pslot, mx_usize_t size)
{
    mx_memory_area_t ret = { mx_null, 0};

    mx_mutex_enter(&pslot->mutex);
    switch (pslot->type)
    {
    case MX_MEMSLOT_TYPE_BLOCK:
        {
            mx_memslot_block_pt bslot = (mx_memslot_block_pt)pslot;
            if ((ret.size = mx_mmblock_blocksize(&bslot->mmblock)) >= size)
            {
                ret.start = mx_mmblock_alloc(&bslot->mmblock);
            }
        }
        break;
    case MX_MEMSLOT_TYPE_FLAT:
        {
            mx_memslot_flat_pt fslot = (mx_memslot_flat_pt)pslot;
            ret.start = mx_mmflat_alloc(&fslot->mmflat, size);
            ret.size = size;
        }
        break;
    }
    mx_mutex_leave(&pslot->mutex);

    return ret;
}

/**
 * free memory to specified memory slot
 * @param pslot: the descriptor of memory slot
 * @param addr: the start address of memory space
 * @return: void
 */
void mx_memslot_free (mx_memslot_pt pslot, mx_void_pt addr)
{
    mx_mutex_enter(&pslot->mutex);
    switch (pslot->type)
    {
    case MX_MEMSLOT_TYPE_BLOCK:
        {
            mx_memslot_block_pt bslot = (mx_memslot_block_pt)pslot;
            mx_mmblock_free(&bslot->mmblock, addr);
        }
        break;
    case MX_MEMSLOT_TYPE_FLAT:
        {
            mx_memslot_flat_pt fslot = (mx_memslot_flat_pt)pslot;
            mx_mmflat_free(&fslot->mmflat, addr);
        }
        break;
    }
    mx_mutex_leave(&pslot->mutex);
}

/**
 * get the information of memory slot
 * @param pslot: the descriptor of memory slot
 * @param info: [O] the pointer to the information
 * @return: void
 */
void mx_memslot_getinfo (mx_memslot_pt pslot, mx_memslot_info_pt info)
{
    info->id = pslot->id;
    info->align = pslot->align;
    info->type = pslot->type;
    info->perf = pslot->perf;

    switch (pslot->type)
    {
    case MX_MEMSLOT_TYPE_BLOCK:
        {
            mx_memslot_block_pt bslot = (mx_memslot_block_pt)pslot;
            info->start = mx_mmblock_startaddr(&bslot->mmblock);
            info->size = mx_mmblock_available(&bslot->mmblock);
            info->max = mx_mmblock_blocksize(&bslot->mmblock);
            info->min = info->max;
        }
        break;
    case MX_MEMSLOT_TYPE_FLAT:
        {
            mx_memslot_flat_pt fslot = (mx_memslot_flat_pt)pslot;
            info->start = mx_mmflat_startaddr(&fslot->mmflat);
            info->size = mx_mmflat_available(&fslot->mmflat);
            info->max = info->size;
            info->min = 1;
        }
        break;
    }
}
